<?php
/**
 * Copyright 2010 Dave Keen
 * http://www.actionscriptdeveloper.co.uk
 * 
 * This file is part of Flextrine.
 * 
 * Flextrine is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * and the Lesser GNU General Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/>.
 * 
 */

namespace Flextrine;

use Doctrine\Common\ClassLoader,
	Doctrine\Common\Cache\ApcCache,
    Doctrine\ORMConfiguration,
    Doctrine\ORM\EntityManager,
	Doctrine\ORM\Events,
	Doctrine\ORM\Mapping\Driver\AnnotationDriver,
	Doctrine\Common\Annotations\AnnotationReader,
	FlextrineConfig,
	Flextrine\Tools\AssociationWalker,
	Flextrine\Tools\FlushExecutor,
	Flextrine\Tools\AS3EntityGenerator;

abstract class AbstractFlextrineService {

	protected $em;
	
	protected $flextrineManagerEnabled = true;
	
	const EAGER = "eager";
	const LAZY = "lazy";
	
	function __construct() {
		// Retrieve the EntityManager from the Config static context.  This will have been already configured in bootstrap.php
		$this->em = Config::$em;
	}
	
	/**
	 * At present I need to call em->clear() before each request, because if AMFPHP queues up multiple requests at once for some reason having something
	 * in the em breaks the next request's class mapping.
	 */
	
	public function load($entityClass, $id, $fetchMode) {
		$this->em->clear();
		
		if ($fetchMode == self::EAGER) {
			return AssociationWalker::loadProxies($this->em->getRepository($entityClass)->find($id), $this->em);
		} else if ($fetchMode == self::LAZY) {
			return AssociationWalker::insertProxyIdentifiers($this->em->getRepository($entityClass)->find($id), $this->em);
		}
	}
	
	public function loadAll($entityClass, $fetchMode) {
		$this->em->clear();
		
		if ($fetchMode == self::EAGER) {
			return AssociationWalker::loadProxies($this->em->getRepository($entityClass)->findAll(), $this->em);
		} else if ($fetchMode == self::LAZY) {
			return AssociationWalker::insertProxyIdentifiers($this->em->getRepository($entityClass)->findAll(), $this->em);
		}
	}
	
	public function loadBy($entityClass, $criteria, $fetchMode) {
		$this->em->clear();
		
		if ($fetchMode == self::EAGER) {
			return AssociationWalker::loadProxies($this->em->getRepository($entityClass)->findBy($criteria), $this->em);
		} else if ($fetchMode == self::LAZY) {
			return AssociationWalker::insertProxyIdentifiers($this->em->getRepository($entityClass)->findBy($criteria), $this->em);
		}
	}
	
	public function loadOneBy($entityClass, $criteria, $fetchMode) {
		$this->em->clear();
		
		if ($fetchMode == self::EAGER) {
			return AssociationWalker::loadProxies($this->em->getRepository($entityClass)->findOneBy((array)$criteria), $this->em);
		} else if ($fetchMode == self::LAZY) {
			return AssociationWalker::insertProxyIdentifiers($this->em->getRepository($entityClass)->findOneBy((array)$criteria), $this->em);
		}
	}
	
	public function select($query, $fetchMode) {
		if (substr(strtoupper($query), 0, 7) != "SELECT ")
			throw new \Exception("You may only use the SELECT operator");
		
		$this->em->clear();
		
		$result = $this->em->createQuery($query)->getResult();
		
		if ($fetchMode == self::EAGER) {
			return AssociationWalker::loadProxies($result, $this->em);
		} else if ($fetchMode == self::LAZY) {
			return AssociationWalker::insertProxyIdentifiers($result, $this->em);
		}
	}
	
	public function selectOne($query, $fetchMode) {
		if (substr(strtoupper($query), 0, 7) != "SELECT ")
			throw new \Exception("You may only use the SELECT operator");
		
		$this->em->clear();
		
		$result = $this->em->createQuery($query)->getResult();
		
		if ($fetchMode == self::EAGER) {
			return AssociationWalker::loadProxies($result[0], $this->em);
		} else if ($fetchMode == self::LAZY) {
			return AssociationWalker::insertProxyIdentifiers($result[0], $this->em);
		}
	}
	
	public function flush($remoteOperations, $fetchMode) {
		$this->em->clear();
		
		// Execute any beforeFlush custom operations
		//foreach ($remoteOperations["beforeFlushCustoms"] as $remoteOperation) {
		foreach ($remoteOperations->beforeFlushCustoms as $remoteOperation) {
			$remoteOperation = (object)$remoteOperation;
			$data = (object)$remoteOperation->data;
			$this->runCustomOperation($remoteOperation->operation, $data);
		}
		
		// Perform the flush
		$flushExecutor = new FlushExecutor($this->em, $remoteOperations);
		$changeSets = $flushExecutor->flush();
		
		// Go through the elements in the change sets making the entities Flextrine ready (apart from temporaryUidMap which is a special array of strings)
		foreach ($changeSets as $changeSetType => $changeSet) {
			if ($changeSetType != "temporaryUidMap") {
				foreach ($changeSet as $oid => $entity) {
					if ($fetchMode == self::EAGER) {
						AssociationWalker::loadProxies($entity, $this->em);
					} else if ($fetchMode == self::LAZY) {
						AssociationWalker::insertProxyIdentifiers($entity, $this->em);
					}
				}
			}
		}
		
		// Execute any afterFlush custom operations
		//foreach ($remoteOperations["afterFlushCustoms"] as $remoteOperation) {
		foreach ($remoteOperations->afterFlushCustoms as $remoteOperation) {
			$remoteOperation = (object)$remoteOperation;
			$data = (object)$remoteOperation->data;
			$this->runCustomOperation($remoteOperation->operation, $data);
		}
		
		// Return the change sets so they can be replicated in Flextrine
		return $changeSets;
	}
	
	protected function runCustomOperation($operation, $data) {
		
	}
	
	/**
	 * The following functions are for use by the Flextrine Manager.  They are only accessible if $flextrineManagerEnabled is true.
	 */
	public function getFlextrineManagerData() {
		if (!Config::$flextrineManagerEnabled)
			throw new \Exception("Flextrine manager access is not enabled");
			
		return array("dbname" => $this->em->getConnection()->getDatabase(),
					 "entities" => $this->getSchemaEntities());
	}
	
	public function generateAS3Entities() {
		if (!Config::$flextrineManagerEnabled)
			throw new \Exception("Flextrine manager access is not enabled");
		
		$as3EntityGenerator = new AS3EntityGenerator($this->getEntityClassesMetaData());
		return $as3EntityGenerator->generate();
	}
	
	public function createSchema() {
		if (!Config::$flextrineManagerEnabled)
			throw new \Exception("Flextrine manager access is not enabled");
			
		$tool = new \Doctrine\ORM\Tools\SchemaTool($this->em);
		$tool->createSchema($this->getEntityClassesMetaData());
	}
	
	public function updateSchema() {
		if (!Config::$flextrineManagerEnabled)
			throw new \Exception("Flextrine manager access is not enabled");
			
		$tool = new \Doctrine\ORM\Tools\SchemaTool($this->em);
		$tool->updateSchema($this->getEntityClassesMetaData());
	}
	
	public function dropSchema() {
		if (!Config::$flextrineManagerEnabled)
			throw new \Exception("Flextrine manager access is not enabled");
			
		$tool = new \Doctrine\ORM\Tools\SchemaTool($this->em);
		$tool->dropSchema($this->getEntityClassesMetaData());
	}
	
	/**
	 * This function returns an array of strings with the fully qualified class of each entity.  The $entitiesPath parameter is no longer used
	 * but will be left in for a little while to maintain backwards compatibility with projects created in Flextrine 0.6.1 and earlier.
	 */
	protected function getSchemaEntities($entitiesPath = null) {
		$entities = array();
		
		foreach ($this->em->getMetadataFactory()->getAllMetadata() as $metadata)
			$entities[] = $metadata->name;
		
		return $entities;
	}
	 
	private function getEntityClassesMetaData() {
		return $this->em->getMetadataFactory()->getAllMetadata();
	}
	
}